Part 8 — ARM code tricks
Let`s say we`re trying to write a program which logs the times at which a RISC OS machine is turned on and off. It needs to be a module, which is loaded in the machine`s boot sequence, and it writes its findings to a log file. It`s easy to get the module to write the time and date when the machine is turned on, since it can do this in its initialisation code. However, we cannot guarantee that the user will shut the machine down properly, so the module can`t know exactly when the machine has had the power cut.
The solution is to make the module continually keep
a record of when it was last 'living' by writing the time into the computer`s
battery-backed memory. Then, when it starts up again, it can re-read
this value, deduce when the machine was turned off and write the information
to the log file. Now comes the hard part: how to implement this in
ARM code. We could use claim TickerV (&1C), a vector called every
centisecond. Then every 6000 calls (i.e. one minute), we write the
value to the CMOS RAM. However, RISC OS provides us with OS_CallAfter
and OS_CallEvery, SWIs which do 'exactly what they say on the tin' (see
figure 1). They are closely bound up with OS_AddCallBack. We
can use OS_Byte 161 / 162 to read and write CMOS RAM; bytes 30-45 are allocated
'for the user'.
The CallBack SWIs
OS_CallAfter
call with: R0 = cs to wait
Calls a CallBack routine after a specified amount of time; OS_CallAfter calls the routine once, OS_CallEvery carries on until told to stop with OS_RemoveTickerEvent. OS_AddCallBack call with: R0 = address to call
This is equivalent to OS_CallAfter, but there is no delay: RISC OS calls the routine as soon as it is in user mode with interrupts enabled. OS_RemoveTickerEvent call with: R0 = address of routine being called
This will stop the OS calling a routine previously registered with OS_CallAfter
or OS_CallEvery; more often used with the latter. It will generate
an error if the values of R0 and R1 are not those passed previously.
|
SWI trapping
You may conceivably have a problem in ARM code that can only be solved by trapping SWIs. Depending on the SWI you want, the solutions start neat and end up very fiddly. Sometimes, RISC OS makes it easy for you, and you can use OS_Claim to claim that SWI‘s vector (see figure 2 for a short list).
Some modules will provide mechanisms for programmers to claim certain SWIs of theirs. The Wimp is an example of this; you can trap Wimp_Poll quite neatly by calling the FilterManager SWIs, as detailed in Part 7, last issue.
Another, slightly messier solution is to create a module with the same SWI chunk as those of the module you`re trying to intercept. WimpSWIVe, by Andrew Clover, does this for the Wimp and provides a 'neat' claiming structure around what is essentially a messy patch (in that only one module can do it at a time).
If you need an OS_ SWI which isn‘t covered by the usual list of vectors, there is a table of vectors for them starting at address &1F033FC in memory which Acorn have kept consistent throughout RISC OS, but not publicised. So to trap OS_CRC (SWI &5B), you should poke address (&1F033FC + (&5B * 4)) with the address of your new routine, preserve its old contents, and branch there when you‘ve finished, preserving all registers.
Otherwise, you must resort to claiming hardware vectors
which constitute the carpeting of RISC OS. Pull them out at your
peril; these deserve an article to themselves due to the different way
they are handled under different processors and versions of the operating
system.
A few SWIs trappable through vectors
The complete list can be found in the Programmers Reference Manual, page 1-78, or in the StrongHelp SWI manual under the OS_ calls list. The vectors are called with the SWI‘s usual parameters (e.g. OS_CLI with R0 pointing to the command string). Return from a vector routine with either MOV PC,R14 to claim the SWI or LDMFD R13!,{pc } to intercept it; with the latter, ensure you have restored any registers stacked previously (including the old R14!). Post-processing (i.e. altering the parameters the SWI returns) can be achieved by calling the SWI from within your routine and intercepting it on return, but be warned that your vector routine will be called again as a result and should do nothing; otherwise the machine will come to an abrupt halt as the SWI is endlessly re-executed. ByteV (vector &06)
FileV (vector &08)
MouseV (vector &1A)
ColourV (vector &22)
|
A bit of S and M?
Under RISC OS 3.7, there is a SWI, OS_CallASWI, whose sole purpose is to call a SWI whose number is passed in R10. There is a point to this; if you remember, all ARM instructions, including each individual SWI number, are four byte instruction. Except for this SWI, there`s no 'neat' way of calling a SWI by number.
One way of doing it is to use self-modifying code. We assemble and store an instruction in a particular address, then run the program counter over it. The fragment of code in figure 2 assembles and runs a SWI OS_CLI instruction.
Before the StrongARM, ARM processors had one cache, a small area of memory on the chip where instructions and data were stored when they were accessed frequently. If the ARM chip needed to refer to a memory location which it had previously cached, it needn‘t bother to look in the main memory, which resulted in a speed increase. The StrongARM has two caches; one for data and one for instructions. The upshot of this is that if we try to modify what the chip thinks of as 'code', we`ll run into problems. In fact, Acorn have decreed that self-modifying code should not be used by RISC OS programs unless it is absolutely necessary. Where a piece of code is altered, you need to use the SWI OS_SynchroniseCodeAreas to tell it which area of memory has potentially changed the code it had (see figure 4).
The other thing to note is the way instructions are assembled in memory:
1110 1111 0000 0000 0000 0000 0000 0101 = &EF000005
The top four bits represent one of sixteen condition
codes (this will always happen). Setting the next four bits high
indicates that it is a SWI instruction; all the lower bits are the SWI
number. Other instructions follow similar patterns; looking at the
way an instruction is represented as a bit-pattern helps to show the problems
faced with immediate constants. Full information is printed in the
Acorn Assembly manual.
This fragment of code assembles and runs a SWI OS_CLI instruction:
MOV R10,#&EF000000
|
OS_SynchroniseCodeAreas (SWI &6E)
Call with: R0 = 0 to synchronise whole memory
This SWI should be called when code has been altered; StrongARMs may need to re-read information in its caches. Any code that modifies code in other parts of memory (which Acorn discourage strongly) should call this for the appropriate address range before trying to execute the new code. It should be noted that this SWI is relatively slow; hence use with caution in speed-dependent programs. |
Next time, I`ll summarise these tips and produce examples of their use, along with a more familiar worked example.