Index


RISC World

Modules for Beginners, Part 5: Claiming Vectors

Brian Pickard

(This is being re-printed from the last issue as the second half of the article went AWOL for some still unknown reason - ED).

Most operating systems use vectors. In RISC OS quite a few of the SWIs use vectors. This means the vectored SWI does not jump directly to its routine held in the OS ROM but RISC OS looks up a memory address held in RAM (this RAM address is the vector reserved for the particular SWI), and jumps to that routine. Usually the address held in the vector is the standard ROM routine.

A designer of a module can claim a vector so that any vectored SWI can then jump to a different routine, hence altering the code for the SWI.

The new routine can do one of the following:

  • Totally replace the original ROM code, in which case control is then passed back to the caller of the SWI.
  • Do some processing then call the ROM code.

  • Call the ROM code then do some processing before control is passed back to the caller of the SWI.

If the new routine replaces the ROM code this is known as intercepting the call, otherwise it is known as passing on the call.

Vector Chains

So far I have been a little simplistic in the explanation of how vectors hold addresses. It is more accurate to visualise a vector as the start of a structure which contains pointers to the next claimant of the vector. This structure holds both the address of the start of the code and a pointer to the workspace for the code.

Because the vector is a structure, more than one routine can claim the vector. The claimant does not have to remember the previous owners address.

Vectors than be claimed and released by many pieces of software in any order. The last claimant of a vector will be the first to have its routine called, then the next most recent claimant and so on. If any of the claimants intercept a vector then all claimants more recent than this one will have there routines called, but none after this one. See the following examples.

Here there are 2 claimants both of them passing on the call.

If a 3rd claimant now intercepts the call then the following happens.

Notice how this claimants interception causes a 'short circuit' so that none of the previous claimants routines are called.

If a 4th claimant who passes on the call is added the following would occur.

How to Claim and Release Vectors

There are two SWI's to Claim and Release vectors called, not suprisingly, OS_Claim and OS_Release. They use registers R0 to R2 as follows.

  • R0 = the vector number (each vector has a unique number)
  • R1 = contains the start address of the code of the claiming or releasing routine
  • R2 = value to be passed in R12 when the routine is called (usually a pointer for workspace).

So to Claim the Mouse vector (number &1A) the following code is added in the modules initialisation.

  MOV R0,#&1A             ;mouse vector number
  ADR R1,routinestartadd% ;start address of routine
  MOV R2,#0               ;R12 would have the value 0 in this case
  SWI "XOS_Claim"         ;call the SWI
,

To release the routine from the vector the same lines are used for R0 to R2 but then call SWI "XOS_Release".

An Example

In the following example the event vector is claimed. This vector is a little more complicated in that it is called whenever an event takes place. Events are generated by several devices, including the keyboard (whenever a key is pressed or released). So using this event we can check what key has been pressed or released.

Each event is numbered the keyboard event being 11 (another useful event is number 10 the mouse button change event). Look at PRM 1-152 onwards for the complete list of events.

To claim this event the following code is used.

  MOV R0,#&1A         ;mouse vector number
  ADR R1,eventstartadd%   ;start address of routine
  MOV R2,R12          ;R12 would have the value of R12 (modules workspace)
  SWI "XOS_Claim"     ;call the SWI

The routine looks at which key has been pressed or released:

.eventstartadd%
  STMFD R13!,{R4,R14} ;push R4 and R14 onto stack
  LDR R4,ctrlchk%     ;look at value of ctrlchk flag
  CMP R4,#0           ;has it been set i.e. has *EventClaimon been issued
  BEQ passon%         ;if not (or *EventClaimoff has cancelled it) then pass on
  CMP R0,#11          ;R0 contains event number if not 11 then pass on
  BNE passon%
  CMP R2,#&61         ;R2 contains the INTERNAL KEY number &61 is the
  BNE passon%         ;right hand CTRL key
  CMP R1,#0           ;is this a key release action
  BNE passon%         ;if not then pass on
  SWI 256+7           ;make a beep
  .passon%
  LDMFD R13!,{R4,PC}  ;restore R4 and return i.e. passon the vector

Note to pass on the vector to the previous owner, use the standard return MOVS PC,R14 or use the stack to load R14 into PC as in the above example. RISC OS placed the previous owners address in R14 on entering the routine.

The above example just makes a beep when the RH CTRL key is RELEASED. You must issue *EventClaimon command to activate this action. This is just done to show how vector routines can stay in the vector chain even if they do nothing. *Event Claimoff switches off the action. The module is called VClaim01 and the full source code VECTMOD1.

Reading a Vectored SWI's Parameters

Some SWI's just return values in registers. It would be convenient if these values could be read or altered in the vector routine before the SWI returns its call to the calling program. But so far the example can only be used to alter or read values before the vector reaches the ROM routine. What is required is the routine to set up a return address within itself before it passes on the call to the next owner. This return address would then be used by the ROM routine instead of the usual return to the calling program.

To do this is a little involved consider the following:

On entering a vector routine the return address of the previous owner is in R14. The return address of the calling program is already on the stack so when intercepting a vector call LDMFD R13!,{PC} is used to return to the calling program. The ROM routine always intercepts its vector.

Our vector routine must push onto the stack a return address after the calling programs return address. The ROM routine will intercept the vector and pull our address off the stack and therefore return to our routine. Any reading/changing of values can then be executed before our routine finally pulls the calling programs return address off the stack and thus return to the calling program.

At first the following might work;

  STMFD R13!,{R12,R14} ;store R12, and the next owners return address on stack
  MOV R12,R13          ;store present value of stack in R12
  STMFD R13!,{PC}      ;store the value of PC+12 (offset due to pipelining)
  MOV R0,R0            ;padding out command because of pipelining
  LDMFD R12,{R12,PC}   ;pass on the call to next owner
  .returnsfromROMtohere
                       ;do any required code on register values
  LDMFD R13!,{R12,R14,PC} ;restore R12 and R14 and returns to calling program

The above code works only for non StrongARM CPU's because StrongARM architecture makes PC point to PC+8 not PC+12 when using STR or STM. So for a StrongARM CPU the following is required:

  STMFD R13!,{R12,R14} ;store R12, and the next owners return address on stack
  MOV R12,R13          ;store present value of stack in R12
  STMFD R13!,{PC}      ;store the value of PC+8 (offset due to pipelining)
  LDMFD R12,{R12,PC}   ;pass on the call to next owner
  .returnsfromROMtohere
                       ;do any required code on register values
  LDMFD R13!,{R12,R14,PC};restore R12 and R14 and returns to calling program

The MOV R0,R0 is not required. There is a neat way round this problem. From RISC OS 3.7 onwards SWI "OS_PlatformFeatures" was included. Calling this in the modules initialisation will show which CPU is in the computer. On return if bit 3 of R0 is set then the CPU stores PC+8 in any STR or STM command otherwise the CPU stores PC+12.

In the example module called VClaim02 (source code VECMOD2) this is done together with the following code which applies to the Mouse vector.

  STMFD R13!,{R12,R14} ;store R12, and the next owners return address on stack
  LDR R12,strngarm%    ;see what this flag contains if 0 then not StrongARM
  CMP R12,#0           ;its value was set in the modules initialisation
  MOV R12,R13          ;store present value of stack in R12
  STMEQFD R13!,{PC}    ;store the value of PC+12 (offset due to pipelining)
  STMNEFD R13!,{PC}    ;store the value of PC+8 (StrongARM CPU)
  LDMFD R12,{R12,PC}   ;pass on the call to next owner
  .returnsfromROMtohere
                       ;do any required code on register values
  LDMFD R13!,{R12,R14,PC};restore R12 and R14 and returns to calling program

The full source code produces a beep when any of the mouse buttons are pressed when *EventClaimon is issued.

Final Example

This claims both the event vector for the keyboard event and the Mouse vector to alter mouse button settings. The source code is in the file VECMOD3 and the module is called VClaim03. This allows the user to press the LEFT ALT key (beep heard) which then modifies mouse vector routine to always have the left mouse button pressed. This produces the effect of having a permanent drag! Pressing LEFT ALT again (beep heard) cancels the effect. The source code is fully annotated.

Well that its for this time, a bit more involved, next time I will show ways in which modules can communicate with BASIC programs and vice versa.

Brian Pickard

 Index