I have always been a big fan of QEMU
To test software or to learn low level programming stuff, it is an invaluable tool. Till recently QEMU was only an x86 emulator. It was able to emulate a full PC and it could run many operating systems unmodified. Recently support for MIPS, ARM, PPC and SPARC emulation has been added. It runs on both Windows and Linux.
I have examined the MIPS emulation and it works for the given test kernel and root file system provided on the downloads page where QEMU itself is available. Learning assembly language on a real system can be rewarding, but an emulator comes close. And since it is just software, you can be more comfortable and not worry too much about system lockup and crashes. Although there are software tools like SPIM to learn MIPS assembly language, QEMU has the advantage of emulated peripherals. You can program the timer, or the interrupt controller, keyboard, serial port, ethernet card among others. Not only that. You have the opportunity to mix assembly and C, use a cross compiler and ‘run’ real code.
So, lets go about doing it. What I did was I grabbed a standalone program that would run on MIPS processors and modified it to run on QEMU. No operating system, just an assembly and C program was cross compiled and used. This program is called barebones and is provided by Jun Sun of Montavista. This program contains an assembly stub for initialization and a bunch of C functions that implement the ‘printf’ function and UART support functions. This code will start, initialize the serial port, print something and drop into an infinite loop. A Makefile is provided for compilation and I used the SDE cross compiler(GCC) available free from MIPS Inc,. So that is the laundry list. The latest version of Qemu, the barebones tar file and the SDE compiler. Should I tell you that you need to install the SDE RPM? I used ‘alien’ to install the SDE RPM on to my Ubuntu machine.
The Qemu MIPS machine is very qeer indeed. Since Qemu was developed as an x86 emulator, it emulates the common PC peripherals and buses. The MIPS machine that it emulates is just like a PC powered by a MIPS processor. For real MIPS machines, look here. There is something that immediately disturbed me. In the x86 based PC architecture, many peripherals are accessed using the ‘in’ and ‘out’ instructions of the x86 CPU. They are on a separate I/O space. In MIPS, there is no separate I/O space, there is just one flat, 4GB memory space. All the peripherals on MIPS systems are memory mapped, meaning peripheral registers are just fixed memory locations. Just write and read to/from them and you have access to their contents. Then I figured something out. There is a Qemu ports base address after which the ‘I/O’ space starts. For example on the PC, the UART registers are available usually in I/O space starting at address 0×3F8. In Qemu, this UART is available at QEMU_PORT_BASE + 0×3F8. So there is no separate IO space or ‘in’ and ‘out’ instructions. Everything is memory mapped. You need to use the load and store instructions used to access memory for this as well.
The first change you’ll need to do is to the uart16550.c file in barebones. Change the definition of BASE to (0xB4000000 + 0×3F8). Here BASE is the starting address of the UART registers. 0xB4000000 is the QEMU_PORT_BASE and this is defined in arch/mips/qemu/q-setup.c in the recent versions of 2.6 kernels the support the MIPS Qemu target. Yes, there is a build option in the MIPS Linux kernel just to support Qemu!
Next, open the Makefile in barebones. Change LOADADDR to 0×80010000. This is the location where Qemu will load the binary and run it. We need the linker to generate addresses keeping this in mind. I have a big endian compiler toolchain, so I will change CROSS_COMPILE to mips-linux- from the original mipsel-linux-. Next on, add ‘test.o’ to the dependencies listed against ‘barebone.elf’. This will allow us to do some printing. Since I am using a big endian compiler, I will also need to change the value of OUTPUT_FORMAT from “elf32-tradlittlemips” to “elf32-tradbigmips”.
Finally, in test.c, remove the ‘#if 0′ and its related #endif around the Uart16550Init() function. Comment out the entire for loop towards the end of the function. You can even call printf_test() in that function finally. Open main.c and add uart_test() as the first thing in the main() function. Run ‘make’ to compile. You must have barebone.elf ready. I did have some problem compiling test.c and it went away by moving the function printf_test() to appear before uart_test().
Here is the output:
shuveb@nida:~/mionmi/barebone$ qemu-system-mips -nographic -kernel barebone.elf
(qemu) qemu: Warning, could not load MIPS bios ‘/usr/local/share/qemu/mips_bios.bin’
x
hello
hello through printf!
test string: test string
test int: -555
test binary: 1000101011
test hex: 0000022b
(main) Hello, world!
loop forever …
Interesting wasn’t it. Next, you can play around by adding code to support the interrupt controller, which is the i8259A and the timer, i8253, more commonly known as the PIT or the Programmable Interval Timer. And you can extend this small little program to do much more interesting things. For debugging, Qemu supports gdb and you can thus single step and see what your code is doing. Writing a simple interrupt handler for the timer interrupt can be a good exercise. Since you have printf() working, it can do wonders to your debugging
I have also provided the barebone program modified to work with Qemu here.
Just Enjoy.