SWI
instruction

 

SWI : SoftWare Interrupt

  SWI<suffix>  <number>
This is a simple facility, but possibly the most used. Many Operating System facilities are provided by SWIs. It is impossible to imagine RISC OS without SWIs.

Nava Whiteford explains how SWIs work (originally in Frobnicate issue 12½)...


In this article I will attempt to delve into the working of SWIs (SoftWare Interrupts).

What is a SWI?

SWI stands for Software Interrupt. In RISC OS SWIs are used to access Operating System routines or modules produced by a 3rd party. Many applications use modules to provide low level external access for other applications.

Examples of SWIs are:

When used in this way, SWIs allow the Operating System to have a modular structure, meaning that the code required to create a complete operating system can be split up into a number of small parts (modules) and a module handler.

When the SWI handler gets a request for a particular routine number it finds the position of the routine and executes it, passing any data.

So how does it work?

Well first lets look at how you use it. A SWI instruction (in assembly language) looks like this:
   SWI &02
or
   SWI "OS_Write0"
Both these instructions are in fact the same, and would therefore assemble to the same instruction. The only difference is that the second instruction uses a string to represent the SWI number which is &02. When a program written using the string is used, the string is first looked up before execution.

We're not going to deal with the strings here as they do not give a true representation of what it going on. They are often used to aid the clarity of a program, but are not the actual instructions that are executed.

Right lets take a look at the first instruction again:

   SWI &02
What does that mean? Well, literally it means enter the SWI handler and pass value &02. In RISC OS this means execute routine number &02.

So how does it do that, how does it passed the SWI number and enter the SWI handler?

If you look at a disassembly of the first 32 bytes of memory (locations 0-&1C) and disassemble them (look at the actual ARM instructions) you should see something like this:

Address  Contents            Disassembly
00000000 : 0..å : E5000030 : STR     R0,[R0,#-48]
00000004 : .óŸå : E59FF31C : LDR     PC,&00000328
00000008 : .óŸå : E59FF31C : LDR     PC,&0000032C
0000000C : .óŸå : E59FF31C : LDR     PC,&00000330
00000010 : .óŸå : E59FF31C : LDR     PC,&00000334
00000014 : .óŸå : E59FF31C : LDR     PC,&00000338
00000018 : .óŸå : E59FF31C : LDR     PC,&0000033C
0000001C : 2 ã : E3A0A632 : MOV     R10,#&3200000
So what? You may think, well take a closer look.

Excluding the first and last instructions (which are special cases) you can see that all the instruction load the PC (Program Counter), which tells the computer where to execute the next instruction from, with a new value. The value is taken from a address in memory which is also shown. (you can take a look at this for yourself using the "Read Memory" option on the !Zap main menu.)

Now, this may seem to bare little relation to SWIs but with the following information it should make more sense.

All a SWI does is change the Mode to Supervisor and set the PC to execute the next instruction at address &08! Putting the processor into Supervisor mode switches out 2 registers r13 and r14 and replaces these with r13_svc and r14_svc.

When entering Supervisor mode, r14_svc will also be set to the address after the SWI instruction.

This is really just like a Branch with Link to address &08 (BL &08) but with space for some data (the SWI number).

As I have said address &08 contains a instruction which jumps to another address, this is the address where the real SWI Handler is!

At this point you maybe thinking "Hang on a minute! What about the SWI number?". Well in fact the value itself is ignored by the processor. The SWI handler obtains it using the value of r14_svc that got passed.

This is how it does it (after storing the registers r0-r12):

  1. It subtracts 4 from r14 to obtain the address of the SWI instruction.
  2. Loads the instruction into a register.
  3. Clears the last 8 bits of the instruction, getting rid of the OpCode and giving just the SWI number.
  4. Uses this value to find to address of the routine of the code to be executing (using lookup tables etc.).
  5. Restore the registers r0-r12.
  6. Takes the processor out of Supervisor mode. [actually, I don't think it does! -Rick]
  7. Jumps to the address of the routine.
Easy! ;)

Here is some example code, from the ARM610 datasheet:

0x08 B Supervisor

EntryTable
 DCD ZeroRtn
 DCD ReadCRtn
 DCD WriteIRtn

 ...

Zero   EQU 0
ReadC  EQU 256
WriteI EQU 512
 
; SWI has routine required in bits 8-23 and data
; (if any) in bits 0-7.
; Assumes R13_svc points to a suitable stack

STMFD R13, {r0-r2 , R14}
 ; Save work registers and return address
LDR R0,[R14,#-4]
 ; Get SWI instruction.
BIC R0,R0, #0xFF000000
 ; Clear top 8 bits.
MOV R1, R0, LSR #8
 ; Get routine offset.
ADR R2, EntryTable
 ; Get start address of entry
 ; table.
LDR R15,[R2,R1,LSL #2]
 ; Branch to appropriate routine.

WriteIRtn
 ; Wnte with character in R0 bits 0 - 7.

.............
 LDMFD R13, {r0-r2 , R15}^
 ; Restore workspace and return, restoring
 ; processor mode and flags.
That's it, that's the basics of the SWI instruction.

 

Sources:

The ARM610 datasheet by Advanced Risc Machines
The ARM RISC Chip - A programmers guide by van Someren Atack published by Addison Wesley

 

 


Thank you Nava.

Now we'll take an example from RISC OS itself...

  1. Processor branches to &00000008 upon a SWI.
     
  2. The instruction there is a branch (old RISC OS) or an LDR into PC (newer RISC OS).
    Processor is redirected...
     
  3. Preserve R10-R12.
     
  4. Mask off bits to get at the SWI call instruction address (was popped into R14).
     
  5. Get the calling SWI command.
     
  6. Mask off the condition code and SWI opcode to leave the SWI itself.
     
  7. Clear the error bit, put zero state in flags.
     
  8. No error bit set? Branch to SWI despatch.
     
  9. Set up for error, and branch to SWI despatch.
     
  10. ...etc...
Use *MemoryI to trace through this. Here is the report for RISC OS 3.70:

*MemoryI 8 +4
00000008 :  E59FF39C : †ôflâ : LDR     PC,&000003AC
This is a 'later' version of RISC OS, so we load an address instead of branching directly to it.


*Memory 3AC +4
Address  :     F E D C     3 2 1 0     7 6 5 4     B A 9 8 :    ASCII Data
000003AC :    01F033C0                                     : Á3ð.            
And this, is the address of our SWI despatch routine.


*MemoryI 1F033C0
01F033C0 :  EA640146 : F.dê : B       &038038E0
01F033C4 :  E3CEC3FF : ÿãã : BIC     R12,R14,#&FC000003
01F033C8 :  E51CB004 : .°.â : LDR     R11,[R12,#-4]
01F033CC :  E3CBB4FF : ÿËã : BIC     R11,R11,#&FF000000 ; =&FF<<24
01F033D0 :  E92D0800 : ..-é : STMFD   R13!,{R11}
01F033D4 :  E3DBB802 : .ã : BICS    R11,R11,#&00020000 ; =1<<17
01F033D8 :  0A6401A6 : .d. : BEQ     &03803A78
01F033DC :  E38EA003 : .‰ã : ORR     R10,R14,#3
01F033E0 :  E33AF000 : .ð:ã : TEQP    R10,#0
01F033E4 :  E35B0017 : ..[ã : CMP     R11,#&17           ; =23 ; *** Not R8-R14
01F033E8 :  135B0034 : 4.[. : CMPNE   R11,#&34           ; ="4" (52)
01F033EC :  13CEE201 : .âÎ. : BICNE   R14,R14,#&10000000 ; =1<<28
01F033F0 :  E35B0C01 : ..[ã : CMP     R11,#&0100         ; =256
01F033F4 :  379FF10B : .ñ.7 : LDRCC   PC,[PC,R11,LSL #2]
01F033F8 :  EA6401B8 : .dê : B       &03803AE0
[...non-SWI stuff snipped...]
Part of the SWI despatch.


*MemoryI 38038E0
038038E0 :  E92D1C00 : ..-é : STMFD   R13!,{R10-R12}
038038E4 :  E14FB000 : .°Oá : MRS     R11,SPSR           ; ARMv3 and later
038038E8 :  E20BC23F : ?â.â : AND     R12,R11,#&F0000003
038038EC :  E18EE00C : .à‰á : ORR     R14,R14,R12
038038F0 :  E20BC0C0 : ÀÀ.â : AND     R12,R11,#&C0       ; ="À" (192)
038038F4 :  E18EEA0C : .ê‰á : ORR     R14,R14,R12,LSL #20
038038F8 :  E10FC000 : .À.á : MRS     R12,CPSR           ; ARMv3 and later
038038FC :  E3CCC01F : .ÀÌã : BIC     R12,R12,#&1F       ; =31
03803900 :  E38CC003 : .À…ã : ORR     R12,R12,#3
03803904 :  E129F00C : .ð)á : MSR     CPSR_c_f,R12       ; ARMv3 and later
03803908 :  EA9BFEAD : -Þ.ê : B       &01F033C4
[...non-SWI stuff snipped...]
The beginning of the SWI despatch calls the code above. This is used on RiscPC-alikes because SWIs occur in SVC32 so the code fudges it to be SVC26 so RISC OS will work.
This doesn't occur on ARM2 or ARM3 machines, and the Iyonix runs in 32bit modes so, again, it won't occur.

Finally, there are two calls into this chunk of code...


*MemoryI 3803A78
03803A78 :  E33FF003 : .ð?ã : TEQP    PC,#3
03803A7C :  E3CEE201 : .âÎã : BIC     R14,R14,#&10000000 ; =1<<28 ; *** Not R8-R14
03803A80 :  E92D4000 : .@-é : STMFD   R13!,{R14}
03803A84 :  E59BB7E4 : ä·.â : LDR     R11,[R11,#&7E4]    ; =2020
03803A88 :  E35B050E : ..[ã : CMP     R11,#&03800000     ; =7<<23
03803A8C :  3A000004 : ...: : BCC     &03803AA4
03803A90 :  292D8000 : ..-) : STMCSFD R13!,{PC}          ; *** Offset not guaranteed
03803A94 :  2A009CDD : ݆.* : BCS     &0382AE10
03803A98 :  E1A00000 : ..á : NOP
03803A9C :  E8BD4000 : .@½è : LDMFD   R13!,{R14}
03803AA0 :  EA9BFE39 : 9Þ.ê : B       &01F0338C
03803AA4 :  E3A0A003 : .ã : MOV     R10,#3
03803AA8 :  EB0016FD : Ý..ë : BL      &038096A4
03803AAC :  EAFFFFFA : úÿÿê : B       &03803A9C
03803AB0 :  E1A0B00E : .°á : MOV     R11,R14
03803AB4 :  EF02010A : ...ï : VDUX    &0A                ; =10
03803AB8 :  7F02010D : .... : VDUVCX  &0D                ; =13
03803ABC :  E1A0E00B : .Àá : MOV     R14,R11
03803AC0 :  EA9BFE31 : 1Þ.ê : B       &01F0338C
03803AC4 :  E1A0A000 : .á : MOV     R10,R0
03803AC8 :  E20B00FF : ÿ..â : AND     R0,R11,#&FF        ; ="ÿ" (255)
03803ACC :  E1A0B00E : .°á : MOV     R11,R14
03803AD0 :  EF020000 : ...ï : SWI     "XOS_WriteC"
03803AD4 :  71A0000A : ..q : MOVVC   R0,R10
*MemoryI 3803AD8
03803AD8 :  E1A0E00B : .Àá : MOV     R14,R11
03803ADC :  EA9BFE2A : *Þ.ê : B       &01F0338C
03803AE0 :  E35B0C02 : ..[ã : CMP     R11,#&0200         ; =512
03803AE4 :  3AFFFFF6 : öÿÿ: : BCC     &03803AC4
03803AE8 :  E92D4200 : .B-é : STMFD   R13!,{R9,R14}
03803AEC :  E20EA33F : ?£.â : AND     R10,R14,#&FC000000 ; =&3F<<26
03803AF0 :  E28FE04B : KÀ·â : ADR     R14,&03803B43
03803AF4 :  E18EE00A : .À‰á : ORR     R14,R14,R10
03803AF8 :  E3CBC03F : ?Àëã : BIC     R12,R11,#&3F       ; ="?" (63)
03803AFC :  E1A0A22C : ,¢á : MOV     R10,R12,LSR #4
03803B00 :  E20AA03F : ?.â : AND     R10,R10,#&3F       ; ="?" (63)
03803B04 :  E59AA790 : .§.â : LDR     R10,[R10,#&790]    ; =1936
03803B08 :  E35A0000 : ..Zã : CMP     R10,#0
03803B0C :  0A000009 : .... : BEQ     &03803B38
03803B10 :  E59A900C : .'.â : LDR     R9,[R10,#&00C]     ; =12
03803B14 :  E159000C : ..Yá : CMP     R9,R12
03803B18 :  159AA008 : ... : LDRNE   R10,[R10,#8]
03803B1C :  1AFFFFF9 : ùÿÿ. : BNE     &03803B08
03803B20 :  E89A1400 : ...è : LDMIA   R10,{R10,R12}
03803B24 :  E59CC00C : .À†â : LDR     R12,[R12,#&00C]    ; =12
03803B28 :  E35C0000 : ..\ã : CMP     R12,#0
03803B2C :  120BB03F : ?°.. : ANDNE   R11,R11,#&3F       ; ="?" (63)
03803B30 :  128CC004 : .À…. : ADDNE   R12,R12,#4
03803B34 :  11A0F00A : .ð. : MOVNE   PC,R10
I'll be nice and leave you to work out what that code does...

 


Return to assembler index
Copyright © 2004 Richard Murray