ARM Integrator-CP PL110 Dirty

From OSDev Wiki
Jump to navigation Jump to search

This page or section refers to its readers or editors using I, my, we or us. It should be edited to be in an encyclopedic tone.

Integrator-CP QEMU PL110 16-Bit Color Example

This is quick and dirty really. I just want to get you on your feet with something that will make you enjoy doing this. Although, many people would consider going straight to working on the GUI not the correct way to do it is however very fun for many people. So here you go.

Prerequisites

Software Why
QEMU We are going to use it. We do not properly initialize the hardware. We almost do it correctly, but not quite enough for the real thing. Thats okay.
C compiler or assembler You need either a compiler or an assembler. I will show you code for doing it both ways. I will handwrite the assembly so it should be easier to read and work with.
objcopy You need something like objcopy on most Linux distributions. What we are going to have to do is copy out the .text section. Like I said this is just really quick and dirty example. We will make a flat binary.

Memory

You will notice I am producing a flat binary output. This output contains no headers and the entry functions begins at the first byte of the image produced. The image is loaded by QEMU to the memory address 0x10000. QEMU supports loading an ELF format image and should correctly place each image section at the specified address. When you write software for real embedded devices they are generally not going to support this as you will write the image directly to their ROM. A many devices have a secondary ROM area that actually provides support when placed in a special mode to allow you to flash your BOOT image which goes in the lower part of the ROM or somewhere different. This essentially consists of two ROMs in essence where the primary boot code allows you to easily flash the device over USB when a special button is held. If not it jumps to your BOOT code. If it can handle non flat binary formats then this special initial ROM code will support this functionality and you can find it in your boards documentation.

But, for the most part you are going to be uploading a flat binary file.

C Code

typedef int(*PFN)(void);

void start(void);


void __attribute__((naked)) entry()
{
	__asm__("mov	sp, #0x60 << 8");
	__asm__("bl	start");
}

#define PL110_CR_EN		0x001
#define PL110_CR_PWR		0x800
#define PL110_IOBASE		0xc0000000
#define PL110_PALBASE		(PL110_IOBASE + 0x200)

typedef unsigned int		uint32;
typedef unsigned char		uint8;
typedef unsigned short		uint16;

typedef struct _PL110MMIO 
{
	uint32		volatile tim0;		//0
	uint32		volatile tim1;		//4
	uint32		volatile tim2;		//8
	uint32		volatile d;		//c
	uint32		volatile upbase;	//10
	uint32		volatile f;		//14
	uint32		volatile g;		//18
	uint32		volatile control;	//1c
} PL110MMIO;

void start(void)
{
	PFN		fn;
	PL110MMIO	*plio;
	int		x;
	uint16		volatile *fb;
	
	plio = (PL110MMIO*)PL110_IOBASE;
	
	/* 640x480 pixels */
	plio->tim0 = 0x3f1f3f9c;
	plio->tim1 = 0x080b61df;
	plio->upbase = 0x200000;
	/* 16-bit color */
	plio->control = 0x1829;
	fb = (uint16*)0x200000;
	for (x = 0; x < (640 * 480) - 10; ++x)
		fb[x] = 0x1f << (5 + 6) | 0xf << 5;
	
	/* uncomment this and the function pointer should crash QEMU if you set it for 8MB of ram or less */
	for(;;);
	fn = (PFN)0x800f20;
	fn();
	return;
}

A naked function implementation so we can setup the stack, which then calls the actual C function start. Then we initialize the card and draw a light blue color across the screen.

To compile it and produce the binary file to use with QEMU first copy the code above into a file named test.c. Then execute the following. Your actual binaries will likely differ. Make sure your using a cross-toolchain for ARM and not your host system unless your host system is ARM.

    ./gcc-arm test.c -nostdlib -o test.o
    ./objcopy-arm -j .text -O binary test.o test.bin
     qemu-system-arm -m 8 -kernel test.bin

If all goes well QEMU will display a light blue screen.


Assembly

main:
	eor	r0, r0, r0
	mov	r0, #0xc0 << 24
		
	mov	r1, #0x3f << 24
	add	r1, r1, #0x1f << 16
	add	r1, r1, #0x3f << 8
	add	r1, r1, #0x9c
	str	r1, [r0, #0]
	
	mov	r1, #0x08 << 24
	add	r1, r1, #0x0b << 16
	add	r1, r1, #0x61 << 8
	add	r1, r1, #0xdf
	str	r1, [r0, #4]
	
	mov	r1, #0x20 << 16
	str	r1, [r0, #0x10]
	
	mov	r1, #0x18 << 8
	add	r1, r1, #0x29
	str	r1, [r0, #0x1c]
	
	mov	r2, #0x4b << 12
	mov	r1, #0x20 << 16
	mov	r3, #0x1f
clearscreen:
	strh	r3, [r1], #2
	subs	r2, r2, #1
	bne	clearscreen
	#b	0x800000
lockme:
	b	lockme

We initialize the card then we draw bright red to the screen.

To assemble it and produce the binary file to use with QEMU first copy the code above into a file named test.asm. Then execute the following. Your actual binaries will likely differ. Make sure your using a cross-toolchain for ARM and not your host system unless your host system is ARM.

     ./as-arm test.asm -o test.o
     ./objcopy-arm -j .text -O binary test.o test.bin
     qemu-system-arm -m 8 -kernel test.bin

If all goes well QEMU will display a bright red screen.

More Flexible Compile/Link

The 0x10000 is the area in memory this image is going to start at.

link.ld

ENTRY (entry)

SECTIONS
{
    . = 0x10000;
    .text : { *(.text*) *(.rodata*) *(.data*) *(.bss*) *(COMMON) }
     _EOI = .;
}

script

You can notice now we are supporting the compile of multiple sources, and a seperate linking step which uses our linker script (above). This build scheme supports properly referencing global variables in your code, but still produces a flat binary file with no header or sections. It also directs any output written to the serial port to stdout.

#!/bin/sh
arm-linux-gnueabi-gcc -Wall -Wextra -Werror -nostdlib -nostartfiles -ffreestanding -std=gnu99 -c *.c
arm-linux-gnueabi-ld -T link.ld -o _armos.bin *.o
arm-linux-gnueabi-objcopy -j .text -O binary _armos.bin armos.bin
qemu-system-arm -m 8 -kernel armos.bin -serial stdio

See ARM_Integrator-CP_Bare_Bones for writing to the serial port.

Explanation

Essentially, what we did was set just enough to make it work with QEMU PC emulator version 0.12.5 (Debian 0.12.5+dfsg-3squeeze1), Copyright (c) 2003-2008 Fabrice Bellard. If it does not work and you have a different version of QEMU then that could be the problem, but then again make sure you used the correct toolchain.

Now, the real hardware. Well its going to want you to set more values then just that. Also, the pixels are 16-bit in the memory buffer at 0x200000 and they are in 565 format BGR. So blue bits occupy the most significant bits and there is 5 of them. B:5 G:6 R:5 - green has 6 bits. This is a common format and used with DXT1 compression format most graphics cards support.

After we initialize the card we just set the same value in a loop. Now, you will notice that in both the C and the assembly code that I have a loop and a commented out branch instruction or in the C code I have a function pointer which will crash the emulator. Its not going to hard crash it, but it will make the emulator stop and display the registers. This is highly useful as a debugging aid. Since you place it somewhere in your program and it will tell you what all the registers contain so you can debug difficult pieces of code. It can also tell you if the code has deadlocked. How else do you know if the code has completed properly or has deadlocked? You do not know unless you make it do something to tell you.

Since you have the ability to plot colored pixels on the screen you have another way to debug your operating system that you are going to build a interesting GUI on top on while completely forgetting about the real interesting stuff like processes, threads, timers, priority, disk I/O, devices, memory management, heaps...

But, having fun is important too it just requires forgetting about all that! So have fun because thats why I made this page.