Tuesday, September 28, 2010

Using the ROLF Emulator to run RISC OS

Updated below.

I've been working on getting RISC OS 4.02 running using the ARM emulator from ROLF.

This was initially prompted by a near death experience of my Risc PC, and also a desire to see how well it would do.

Also, many years ago, I suggested an approach to getting RO available on (then-current and 26-bit mode supporting) ARM based computers.

It was quite simple:
1. Identify what the OS absolutely needed from the hardware to be able to run (I guessed memory, one or two timers and some form of static memory, as provided by the CMOS RAM in the RPC)

2. Get the hardware manufacturers to provide some startup code to get that hardware running and enter the OS with information about where the RAM/ROM is and a set of pointers to routines to provide each of the required functions.

3. At a certain point in the boot process, the hardware manufacturers could start modules to handle other aspects of their computer.

It looks, so far, like that approach would have worked. (And, of course, getting the hardware manufacturers to work on the low level aspects would have had the advantage of hugely increasing the number of engineers working on getting the OS to market, at no cost to ROL.)

The speed of running the ArtWorks Renderer module on some complex images under the emulator show a speed of approximately 3-4 times that of a 200MHz StrongARM Risc PC. (i.e. celtic_knot3 renders in about 14s, as opposed to nearly a minute on the RPC). There's still some considerable scope for improvements in the code generated by the emulator.

As far as I can tell from reading about RPCEmu (I've had difficulties installing it, and not yet suceeded), the speed compares quite well.

The main difference, I think, is that ROLF uses the Linux process' memory to provide the emulated RISC OS task with no address translation required. With a single RISC OS Task (process) that is very easy, since memory doesn't have to be switched in and out, as is the case with the whole OS.

The approach I've taken is to use some special link parameters to keep the Linux process's memory in an area of the memory map which RISC OS doesn't use. Then, a set of files for ROM, RAM and VRAM are mapped in and out of memory as required by the OS using mmap (this is the part that could turn out to be horrendously slow).

Also, I've avoided emulating hardware outside the processor (so it's not really a RiscPC emulator) and chosen to patch some sections of RISC OS with native code. This is an ugly approach, but it has the advantage of being quick and easy as well as identifying those essential hardware elements I was talking about. Longer term it should be possible to integrate RPCEmu's hardware emulation routines (both products are GPL licenced).

Update:
It seems I've found a problem with Linux. The emulator, when it's producing a lot of debug output, tends to hang in a futex system call. This happens with the shared C library, the static equivalent that came with Knoppix and a specially compiled static glibc-2.5.1. Kernel 2.6.32.6.

The other problem is with RISC OS; the routine TranslateMonitorLeadType (which I will be replacing with native code, but that's not the point) makes an XOS_ServiceCall, which causes a jump to zero. I'll look into that before I try to work out the Linux Kernel.

Update 2: It seems the ROM files were corrupted somehow.

Update 3: Yes, there really is a problem with Linux. The following program will hang, sooner or later, on my system. It's easiest to see if you pipe the output into "grep -n Tick". It is probably related to this.


#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>

#include <stdint.h>

static void alrm_action( int code, siginfo_t *info, void *p )
{
fprintf( stderr, "Tick" );
}

int main()
{
struct sigaction action = { .sa_sigaction = alrm_action, .sa_flags = SA_SIGINFO };
sigaction( SIGALRM, &action, 0 );

int microseconds = 10000; // 1cs

struct itimerval timer_val = {
.it_interval = { .tv_sec = microseconds / 1000000, .tv_usec = microseconds % 1000000 },
.it_value = { .tv_sec = microseconds / 1000000, .tv_usec = microseconds % 1000000 },
};
struct itimerval old_val;

setitimer( ITIMER_REAL, &timer_val, &old_val );

int x = 0;
while (1) {
fprintf( stderr, "+" );
if (x-- == 0)
{
fprintf( stderr, "\n" );
x = 100;
}
}

return 0;
}


Update 3: It's not a problem with Linux, fortunately. This document lists 118 functions "that shall be either reentrant or non-interruptible by signals and shall be async-signal-safe"; printf variations are not included.

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home