ZX Murmulator OS

ZX Murmulator is a single-board ultra-cheap microcomputer based on the Raspberry Pi Pico board (hereinafter referred to as “pico”), which, in turn, is based on the RP2040 microcontroller.

RP2040 is one of the most famous dual-core implementations of ARM Cortex-M0+ with 264 KB of built-in SRAM memory and from 2 to 16 MB of flash memory connected via the QSPI interface, soldered on the board of the peak. This microcontroller easily overclocks to 400 MHz without any radiator, despite its standard 133. Which allows you to run quite gluttonous tasks on it.

This is the speed of the RP2040 chip 378 MHz with FPU emulation, the test was run under Murmulator OS.

This is the speed of the RP2040 chip 378 MHz with FPU emulation, the test was run under Murmulator OS.

In the previous article (https://habr.com/ru/articles/839960/) I mentioned that a proprietary operating system is currently being developed for the Murmulator. The question arises – why does a microcomputer with 264 KB of memory need an OS at all?

The main task of any operating system is to provide applications with a unified way of accessing hardware resources, and to allow the user to start and stop running applications.

In fact, almost all applied tasks of Murmulator can be solved without any OS – it is enough to install a bootloader (see. https://github.com/xrip/pico-launcher), which will allow you to manage firmware by loading them from an SD card. The main disadvantage of the bootloader – the inability to edit emulator config files is easily solved by writing it. But what if you need something else? You can write a separate firmware for each sneeze, or modify the existing one. That is, Murmulator OS (hereinafter – simply MOS) should solve this problem – provide a more flexible way to expand functionality.

What else can an operating system provide? – Unification of access to equipment. Currently, each individual firmware contains all the device drivers necessary for the application to function, i.e. there is obvious redundancy. If the OS contains all the necessary drivers, the application will be able to concentrate only on its functionality, and request the rest from the OS.

Another additional benefit of having some OS for the device is the ability to use this device to teach children the basic concepts of computer science. And given that Murmulator is initially a gaming platform for which there are many retro computer emulators, you can introduce the kids to the history of the development of computing technology, show games from the 80s and early 90s of the last century of various platforms, countries and concepts.

It is also worth remembering that the Murmulator is an ultra-budget solution, i.e. almost anyone can afford such a device. Getting a PS/2 keyboard and a VGA monitor is also an extremely budget task these days. That is, in this “offline mode”, the child will only need to get the firmware files (emulators) and retro games for them somewhere. I think that our businessmen who sell ready-made Murmulators can very well provide them with an SD card, on which the entire collection will already be recorded. Well, this does not apply to the OS itself.

When I first thought about writing an OS for Murmulator, I had an idea that it would be possible to create entire computer classes of Murmulators, but after looking through (diagonally) the computer science curriculum at school, I realized that it was too late, because no one needed it anymore, they teach you to drag windows around the desktop and edit tables in Excel. Well, maybe the program will change, and they will start teaching the basics. Or it will be useful in retro-computer clubs. Are there any? If not, I give you a business idea ))

Let's get back to the OS itself. When choosing a kernel for the future OS, I shoveled through a bunch of ready-made code, in which something did not suit me. The closest to the required parameters was FreeRTOS (https://github.com/FreeRTOS/FreeRTOS-Community-Supported-Demos/tree/3d475bddf7ac8af425da67cdaa2485e90a57a881/CORTEX_M0%2B_RP2040), the only drawback of which is its advantage – it is very simple and at the same time extremely insufficient. That is, you cannot download a distribution of this OS for RP2040 somewhere and install it on some equipment, even if it is incompatible with Murmulator. FreeRTOS, in this case, is an embedded solution, i.e. if you need multitasking, it will provide it for you, but with file operations, keyboard drivers, video subsystem, sound and any other sneeze – it's all on you.

Why did I need multitasking at all? Look, ancient MS DOS or Windows 3.1 managed without it, and nothing happened. It is very difficult to explain this to a person who has never written programs for an OS with voluntary multitasking. I will say briefly – it is extremely tedious and extremely unreliable. There is no guarantee that your program will get access to the resource in time. That is, you need to write something to the sound card buffer, but no – the disk formatting program has taken up all the resources at this time and does not give them back, and the sound card at this time outputs one note, because no one has supplied it with a new note. In general, I chose the kernel of the future OS for myself, and began to actively cover it with drivers for available devices.

A few more words should be said about ARM Cortex M0+ – this core does not have a full-fledged MMU – only a primitive MPU and, in this case, XIP, which is of little use to us. That is, you can immediately forget about virtual memory. An attempt to build it on one MPU will lead to such slowdowns that you can simply install the IBM PC XT emulator and get similar performance.

The absence of a dedicated address space for a process with no address translation is a death sentence for non-relocatable programs. That is, after compilation we usually get an object file that is not yet bound to any addresses in memory (except for equipment addresses), but after linking – that's it, the addresses are nailed to the resulting code. Moreover, it is no longer possible to separate which addresses are bound “for a reason”, since they refer to the addresses of some equipment, and which are “just like that”, because the linker wanted it that way, while some of the addressing will be normal – relative.

Rewriting the linker is clearly not my level of work (or would require disproportionate time), so it was decided to get rid of it. Completely. Perhaps, in the future, I will write a linker for MOS, as well as a compiler for it, and everything else… but for now there is none (suitable). Accordingly, at this stage we will be satisfied with the result of compiling the program into an object file. Now the task of the OS is to place the code from the object file somewhere in free memory, resolve addresses and transfer control to the function main.

The question arises – how will the program access the API of the OS itself? Well, since the OS is a regular .uf2 firmware, in which function addresses can be nailed to specific addresses by specifying them in the .ld file, there is no problem, but this is an extremely cumbersome and inconvenient solution – to specify the address for each API function. It is better to make a table of function addresses (pointers to them), nail its beginning to a specific address, and let the linker resolve the content itself.

In this case, it is enough for applications to provide a header file containing constructs like:

#define M_OS_API_SYS_TABLE_BASE ((void*)(0x10000000ul + (16 << 20) - (4 << 10)))
static const unsigned long * const _sys_table_ptrs = (const unsigned long * const)M_OS_API_SYS_TABLE_BASE;

inline static int kill(uint32_t task_n) {
typedef int (*fn_ptr_t)(uint32_t);
return ((fn_ptr_t)_sys_table_ptrs[244])(task_n);
}

which will allow you not to worry about the actual addresses of the functions, they are stored in the same pointer table, which is nailed to the desired address.

It should be noted that due to the Cortex-M0+ features, all MOS applications will share common memory, which means a decrease in the reliability of the entire system. Unfortunately, I have not yet come up with a good solution to this problem. Maybe later I will return to this and try to use the existing MPU in the processor to prevent access to “foreign” memory, but for now this functionality simply does not exist, and this must be taken into account when developing programs.

It is also necessary to take into account that if some program does something “forbidden” that will lead to a HardFault of the processor core, then no FreeRTOS will help here – the core will stop – i.e. hang. I have a number of ideas for intercepting HardFault and then deleting the task context, but this has not yet been implemented either.

The SIGKILL analogue for tasks has not yet been implemented either; instead, there is a SIGTERM analogue, which assumes that the program itself will process this signal (for this, there is a predefined “signal” handler, to which the logic for exiting the task should be attached).

There is a possibility that with the release of Raspberry Pi Pico 2, on the RP2350 chip (2 x ARM Cortex-M33) all the problems can be solved by standard virtualization, and this piece of OS implementation will have to be thrown out. But this will be another Murmulator (2.0?)…

The MOS sources can be studied here: https://github.com/DnCraptor/murmulator-os

A little later I will post another article about MOS from the user's point of view.

MOS plays .wav file

MOS plays .wav file

Murmulator Commander for MOS

Murmulator Commander for MOS

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *